iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0
Mobile Development

從開發瀏覽器 APP 學習 Android 實戰技巧,並搭上 Jetpack Compose 的列車系列 第 6

[Day6] 從開發瀏覽器 APP 學習實戰技巧 -- 如何使用自訂字型瀏覽網頁

  • 分享至 

  • xImage
  •  

去年參加鐵人賽時,已經寫了一篇關於字型調整的文章:電子書閱讀器上的瀏覽器 [Day08] 調整網頁字型。當時介紹了怎麼調整字型粗細,大小,以及如何套用雲端字型。但是,其實我的終極目標是跟電子書閱讀 App 一樣:在設備上安裝更多的字型,然後在 App 中可以任意選擇自己的字型檔。

雖然很久以前我就有試著解決這問題,無奈網上的解決方案都是繞著使用編譯到 apk 中 assets 目錄下的固定字型。這方式無法套用到使用者自行放到手機上的字型檔案;我也不想因此在 2.5 MB 大小的 APP 中,放入一個動輒 10 到 30 MB 的字型檔案,這樣有點太本末倒置了。

在不斷地探索之後,終於讓我試出了一套可行的方式。雖然還不是很完美的方案,但已經有八九十分了吧。希望以後有機會再把它變得更完美。

實作

簡單來說:要讓使用者先選擇一下字型檔案的位置;在 WebView 中注入 CSS style,自定義一個字型,把它的 src 指定到一個特別的 uri;當 WebView 在讀取這個 CSS font style 時,會經過 WebViewClientshouldInterceptRequest() ,這時要把字型檔的 binary stream 餵進去。下面就是詳細的步驟內容。

步驟一:從系統中選擇想要的字型檔案

這步驟不難,只是因為要讓使用者選擇字型的話,還是要把這部分的 UI 邏輯先做好。我寫了一個 openFontFilePicker 的函式,叫起系統的 file picker。

https://ithelp.ithome.com.tw/upload/images/20220916/20140260Kodi9uQOLO.png

ActivityFragment 收到系統回傳的 intent 時,我會把它記錄到 SharedPreferences 中,並且利用第 71, 72 行,取得之後 APP 繼續能夠讀取這個檔案的權限。

https://ithelp.ithome.com.tw/upload/images/20220916/20140260XoOqV9rO1Z.png

塞在 SharedPreferences 的字型資料,寫了一個簡單的 class 儲存檔名和收到的 content uri。

https://ithelp.ithome.com.tw/upload/images/20220916/20140260WfQgSQvloR.png

https://ithelp.ithome.com.tw/upload/images/20220916/2014026073P7G6ULWc.png

步驟二:注入 CSS Font 資訊到 WebView 中

注入的方式,網路上倒是有不少文件,而且其實都寫得大同小異。我定義了一段要注入的 script 如下:新增一個 font-face,它的 font-familycustomfont,來源是隨便訂的 url(‘mycustomfont’),然後把所有 body 的字型全換成我的 customfont。補充說明一下,在參考加入 Google Web fonts 時(https://fonts.googleapis.com/css2?family=Noto+Serif+TC:wght@400&display=swap),看到 Google 字型設定設定中都會加入 font-display: swap; 後來發現,加入後畫面在更換字型時就不會再閃爍。更多的說明可以參見 Controlling Font Performance with font-display

https://ithelp.ithome.com.tw/upload/images/20220916/201402604LuGY2hO6k.png

注入 CSS 的方式之前我也有包成一個函式了:

https://ithelp.ithome.com.tw/upload/images/20220916/20140260kuD6pV6F0y.png

步驟三:攔截 WebRequest,傳回字型資訊

利用 injectCSS,將 customFontCSS 注入後,接著要處理 intercept Web Request:在 WebViewClient 中覆寫 shouldInterceptRequest()。主要處理落在下圖的第 5行到 17 行:如果來的 request url 是事前定義好的 mycustomfont 的話,這就是 Web 要來找字型資訊的時機,10, 11 行透過 context.contentResolver 建立檔案串流,再生成一個 WebResourceResponse 回傳。

https://ithelp.ithome.com.tw/upload/images/20220916/20140260XPHlOpNRVK.png

基本上這樣子就可以正確地看到手機上的字型檔案有被載入,用來顯示網頁的內容。

未來需要改進的地方

步驟三可以看到,每次網頁來要求字型資訊時,我都是建立新的 inputstream,回傳一個新的 WebResourceResponse。這邊我有嘗試著 re-use inputstream 或是 WebResourceResponse,但都還沒有成功。所以,也就只能先停在這兒。至少這已經是個可以運作的方案了。

另外一點是,很多網頁的字型都會在不同的 element 中另外設定。這種情況下,直接改 body 的字型會沒有作用,這也造成大部分網頁可以;但少部分的網頁還是會維持原來的字型。這問題稍微可以透過把網頁轉成 Reader mode 來解決,但畢竟不是一個很漂亮的作法。也是希望未來會有更完美的處理方式。

示範畫面

https://ithelp.ithome.com.tw/upload/images/20220916/2014026036iAaTkhKh.jpg

https://ithelp.ithome.com.tw/upload/images/20220916/20140260O3SLjsbGFP.jpg

https://ithelp.ithome.com.tw/upload/images/20220916/20140260EQEkvcWNCs.jpg

程式碼連結

大部分的程式碼都在這個 commit 了,雖然後來還有些小修改。


上一篇
[Day5] 從開發瀏覽器 APP 學習實戰技巧 -- 儲存 epub 文件的流程改善
下一篇
[Day7] 從開發瀏覽器 APP 學習實戰技巧 -- 支援 epub 檔案的閱讀
系列文
從開發瀏覽器 APP 學習 Android 實戰技巧,並搭上 Jetpack Compose 的列車31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言